﻿/***********************************************************************************************
COPYRIGHT 2008 Vijeth D

This file is part of Handwritten Character Recognition - NeuronDotNet Sample.
(Project Website : http://neurondotnet.freehostia.com)

NeuronDotNet is a free software. You can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.

NeuronDotNet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with NeuronDotNet.
If not, see <http://www.gnu.org/licenses/>.

***********************************************************************************************/

using System;
using System.Drawing;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
using NeuronDotNet.Controls;
using NeuronDotNet.Core;
using NeuronDotNet.Core.Backpropagation;
using System.Collections;
using System.Collections.Generic;

namespace NeuronDotNet.Samples.CharacterRecognition
{
    public partial class MainForm : Form
    {
        Alphabet currentLetter = null;
        Words words;

        private static readonly string[] letters = 
        {
            "A", "B", "C", "D", "E", "F", "G", "H", "I",
            "J", "K", "L", "M", "N", "O", "P", "Q", "R",
            "S", "T", "U", "V", "W", "X", "Y", "Z"
        };

        public MainForm()
        {
            InitializeComponent();
            words = new Words();
        }

        private void LoadLetter(object sender, EventArgs e)
        {
            currentLetter = Alphabet.GetLetter(cboAplhabet.SelectedIndex);
            SetLabels();
        }

        private void AddInstance(object sender, EventArgs e)
        {
            currentLetter.AddInstance(picTraining.Letter);
            SetLabels();
        }

        private void RemoveInstance(object sender, EventArgs e)
        {
            currentLetter.RemoveCurrentInstance();
            SetLabels();
        }

        private void RemoveAll(object sender, EventArgs e)
        {
            if (MessageBox.Show("Are you sure?", "Confirm Removal", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                currentLetter.RemoveAll();
                SetLabels();
            }
        }

        private void ClearImage(object sender, EventArgs e)
        {
            btnClearImage.Enabled = false;
            btnAdd.Enabled = false;
            btnRemove.Enabled = false;
            picTraining.Letter = new Letter();
            picTraining.Invalidate();
            lblOf.Text = "New";
        }

        private void MoveNext(object sender, EventArgs e)
        {
            currentLetter.MoveNext();
            SetLabels();
        }

        private void MovePrevious(object sender, EventArgs e)
        {
            currentLetter.MovePrevious();
            SetLabels();
        }

        private void SetLabels()
        {
            if (this.currentLetter.InstancesCount > 0)
            {
                lblOf.Text = (currentLetter.InstanceIndex + 1) + " of " + currentLetter.InstancesCount.ToString();
                btnPrev.Enabled = true;
                btnNext.Enabled = true;
                btnClearImage.Enabled = true;
                btnRemoveAll.Enabled = true;
                btnRemove.Enabled = true;
                btnAdd.Enabled = true;
                picTraining.Letter = currentLetter.CurrentInstance;
            }
            else
            {
                lblOf.Text = "0 of 0";
                btnPrev.Enabled = false;
                btnNext.Enabled = false;
                btnClearImage.Enabled = false;
                btnRemove.Enabled = false;
                btnRemoveAll.Enabled = false;
                btnAdd.Enabled = false;
                picTraining.Letter = new Letter();
            }
            picTraining.Invalidate();
        }

        private void Clear(object sender, EventArgs e)
        {
            picCompressed.Invalidate();
            picRecognition.Letter = new Letter();
            picRecognition.Invalidate();
            lblResult.Visible = false;
            lblPreResult.Visible = false;
        }

        private void TrainingPicMousedown(object sender, MouseEventArgs e)
        {
            btnAdd.Enabled = true;
            btnRemove.Enabled = false;
            btnClearImage.Enabled = true;
            lblOf.Text = "New";
        }

        private void RecognitionPicMousedown(object sender, MouseEventArgs e)
        {
            lblResult.Visible = false;
            lblPreResult.Visible = false;
        }

        private void Train(object sender, EventArgs e)
        {
            btnTrain.Enabled = false;

            int cycles = 200;
            if (!int.TryParse(txtCycles.Text, out cycles)) { cycles = 200; }
            txtCycles.Text = cycles.ToString();

            int currentCombination = 0;
            int totalCombinations = Alphabet.LetterCount * (Alphabet.LetterCount - 1) / 2;

            for (int i = 0; i < Alphabet.LetterCount; i++)
            {
                for (int j = i + 1; j < Alphabet.LetterCount; j++)
                {
                    ActivationLayer inputLayer = new LinearLayer(400);
                    ActivationLayer hiddenLayer = new SigmoidLayer(4);
                    ActivationLayer outputLayer = new SigmoidLayer(2);
                    new BackpropagationConnector(inputLayer, hiddenLayer);
                    new BackpropagationConnector(hiddenLayer, outputLayer);
                    BackpropagationNetwork network = new BackpropagationNetwork(inputLayer, outputLayer);

                    TrainingSet trainingSet = new TrainingSet(400, 2);
                    Alphabet ithLetter = Alphabet.GetLetter(i);
                    Alphabet jthLetter = Alphabet.GetLetter(j);
                    foreach (Letter instance in ithLetter.Instances)
                    {
                        trainingSet.Add(new TrainingSample(instance.GetEquivalentVector(20, 20), new double[] { 1d, 0d }));
                    }
                    foreach (Letter instance in jthLetter.Instances)
                    {
                        trainingSet.Add(new TrainingSample(instance.GetEquivalentVector(20, 20), new double[] { 0d, 1d }));
                    }

                    progressTraining.Value = 100 * currentCombination / totalCombinations;

                    Application.DoEvents();

                    bool correct = false;

                    int currentCycles = 35;
                    int count = trainingSet.TrainingSampleCount;

                    while (correct == false & currentCycles <= cycles)
                    {
                        network.Initialize();
                        network.Learn(trainingSet, currentCycles);
                        correct = true;
                        for (int sampleIndex = 0; sampleIndex < count; sampleIndex++)
                        {
                            double[] op = network.Run(trainingSet[sampleIndex].InputVector);
                            if (((trainingSet[sampleIndex].OutputVector[0] > trainingSet[sampleIndex].OutputVector[1]) && op[0] - op[1] < 0.4) || ((trainingSet[sampleIndex].OutputVector[0] < trainingSet[sampleIndex].OutputVector[1]) && op[1] - op[0] < 0.4))
                            {
                                correct = false;
                                trainingSet.Add(trainingSet[sampleIndex]);
                            }
                        }
                        currentCycles *= 2;
                    }

                    lstLog.Items.Add(cboAplhabet.Items[i] + " & " + cboAplhabet.Items[j] + " = " + network.MeanSquaredError.ToString("0.0000"));
                    lstLog.TopIndex = lstLog.Items.Count - (int)(lstLog.Height / lstLog.ItemHeight);
                    try
                    {
                        using (Stream stream = File.Open(Application.StartupPath + @"\Networks\" + i.ToString("00") + j.ToString("00") + ".ndn", FileMode.Create))
                        {
                            IFormatter formatter = new BinaryFormatter();
                            formatter.Serialize(stream, network);
                        }
                    }
                    catch (Exception)
                    {
                        MessageBox.Show("Failed to save trained neural networks", "Critical Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return;
                    }
                    currentCombination++;
                }
            }
            progressTraining.Value = 0;
            btnTrain.Enabled = false;
        }

        private void Recognize(object sender, EventArgs e)
        {
            lblResult.Visible = false;
            lblPreResult.Visible = false;

            int winner = 0;
            int current = 1;

            lstSimilarityTests.Items.Clear();
            picCompressed.Invalidate();
            lblResult.Text = "";

            string word = String.Empty;
            List<Letter> letterList = words.GetLetters(picRecognition.Letter);

            foreach (Letter letter in letterList)
            {
                double[] letterVector = letter.GetEquivalentVector(20, 20);

                winner = 0;
                current = 1;

                while (current < Alphabet.LetterCount)
                {
                    try
                    {
                        using (Stream stream = File.Open(Application.StartupPath + @"\Networks\" + winner.ToString("00") + current.ToString("00") + ".ndn", FileMode.Open))
                        {
                            IFormatter formatter = new BinaryFormatter();
                            INetwork network = (INetwork)formatter.Deserialize(stream);

                            double[] output = network.Run(letterVector);
                            string result = letters[winner] + " vs " + letters[current] + " = ";
                            if (output[1] > output[0])
                            {
                                winner = current;
                            }
                            result += letters[winner];
                            lstSimilarityTests.Items.Add(result);
                            lstSimilarityTests.TopIndex = lstSimilarityTests.Items.Count - (int)(lstSimilarityTests.Height / lstSimilarityTests.ItemHeight);
                        }
                        current++;
                    }
                    catch (Exception)
                    {
                        MessageBox.Show("Failed to load saved neural networks", "Critical Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return;
                    }
                }

                word += letters[winner];

            }
            lblResult.Text = word;
            lblResult.Visible = true;
            lblPreResult.Visible = true;
        }

        private void LoadForm(object sender, EventArgs e)
        {
            cboAplhabet.Items.AddRange(letters);
            cboAplhabet.SelectedIndex = 0;
        }

        private void DrawCompressed(object sender, PaintEventArgs e)
        {
            e.Graphics.Clear(Color.White);

            if (picRecognition.Letter.StrokesCount == 0)
                return;

            List<Letter> letters = words.GetLetters(picRecognition.Letter);
            List<double[]> bitArrays = new List<double[]>();
            foreach (Letter letter in letters)
                bitArrays.Add(letter.GetEquivalentVector(20, 20));

            for (int n = 0; n < bitArrays.Count; n++)
            {
                using (Bitmap map = new Bitmap(20, 20))
                {
                    int k = 0;
                    for (int i = 0; i < 20; i++)
                    {
                        for (int j = 0; j < 20; j++)
                        {
                            int darkness = (int)((1 - bitArrays[n][k++]) * 255);
                            map.SetPixel(j, i, Color.FromArgb(darkness, darkness, darkness));
                        }
                    }
                    e.Graphics.DrawImage(map, n * 20 + 1, 1, 20, 20);
                }
            }
        }
    }
}